<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgl - materials - depth peel</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<style>
			body {
				font-family: Monospace;
				background-color: #000;
				color: #fff;
				margin: 0px;
				overflow: hidden;
			}

			a {
				color: #ffa;
				font-weight: bold;
			}

			#info {
				color: #fff;
				position: absolute;
				top: 10px;
				width: 100%;
				text-align: center;
				z-index: 100;
				display:block;
			}
		</style>
	</head>

	<body>
		<div id="info">
			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl depth peel demo by <a href="http://dusanbosnjak.com" target="_blank" rel="noopener">pailhead</a>.<br />
			<div id="DEPTH_BITS"/>
		</div>

		<script src="./threeOld2.js"></script>

		<script src="./OrbitControlsOld.js"></script>

		<script src="./WebGL.js"></script>

		<script src="./dat.gui.min.js"></script>

		<script src="./stats.min.js"></script>

		<script>

			if ( WEBGL.isWebGLAvailable() === false ) {

				document.body.appendChild( WEBGL.getWebGLErrorMessage() );

			}

			const dpr = 1
			// const dpr = window.devicePixelRatio

			console.log('dpr is ', dpr)

			var renderer, scene, camera, gl;

			var	gui = new dat.GUI( { width: 350 } );

			var stats

			var debugScene, debugCamera
			var depthMaterial, depthTarget, compositeMaterial, gl

			var paintMaterial

			var compositeScene = new THREE.Scene()

			var transparentObjects = []
			var opaqueObjects = []
			var targets = []

			var options = {
				layers: 1,
				opacity: 0.5,
				doubleSide: false,
				enabled: true,
				monochrome: false,
				debug: false,
			}

			var globalPeelUniforms = { 
				uLayer: { value: 0 },
				uPrevDepthTexture: { value: null },
				uPrevColorTexture: { value: null },
				uScreenSize: { value: new THREE.Vector2(1,1) },
				uDepthPeel: { value: Number(options.enabled) },
				uDepthOffset: { value: 0 },
			} 

			gui.add(options, 'layers', 0, 8).step(1)
			gui.add(options, 'opacity', 0, 1).onChange(onOpacityChange)
			gui.add(options, 'enabled').onChange(onModeChange)
			gui.add(options, 'doubleSide').onChange(onSideChange)
			gui.add(options, 'monochrome').onChange(onMonochromeChange)
			gui.add(options, 'debug')

			function onOpacityChange(val){
				transparentObjects.forEach(o=>o.material.opacity=val)
			}

			function getColorKey(){
				return options.monochrome ? 'colorB' : 'colorA'
			}

			function onMonochromeChange(){
				var color = getColorKey()
				transparentObjects.forEach(o=>{
					o.material.color.copy(o.material[color])
				})
			}

			function onSideChange(val){ 
				
				var side = getSide()

				depthMaterial.side = side
				depthMaterial.needsUpdate = true

				scene.traverse(o=>{
					if(o.material) {
						o.material.side = side
						o.material.needsUpdate = true
					}
				})
			}

			function onModeChange(){
				transparentObjects.forEach(o=>{
					o.material.transparent = !options.enabled
				})
				globalPeelUniforms.uDepthPeel.value = Number(options.enabled) 


				opaqueObjects.forEach(o=>o.visible = true)
				transparentObjects.forEach(o=>o.visible = true)
			}

			function getSide(){
				return options.doubleSide ? THREE.DoubleSide : THREE.FrontSide 
			}

			function depthOnbeforeCompile(shader){

				shader.uniforms.uScreenSize = globalPeelUniforms.uScreenSize
				shader.uniforms.uPrevDepthTexture = globalPeelUniforms.uPrevDepthTexture
				shader.uniforms.uLayer = globalPeelUniforms.uLayer
				shader.uniforms.uDepthOffset = globalPeelUniforms.uDepthOffset
				shader.uniforms.uDepthPeel = globalPeelUniforms.uDepthPeel
				
				shader.fragmentShader = `
					uniform vec2 uScreenSize;
					uniform sampler2D uPrevDepthTexture;
					uniform int uLayer;
					uniform int uDepthPeel;
					uniform float uDepthOffset;

					${shader.fragmentShader}
				`
				//peel depth
				shader.fragmentShader = shader.fragmentShader.replace(
				//	/}$/gm,
					/void ?main\(\) ?\{/,
					`
					void main() { 	
						if(uDepthPeel == 0) return;

						if(uLayer != 0 ){
						
						    vec2 screenPos = gl_FragCoord.xy * uScreenSize;
						    
						    float prevDepth = unpackRGBAToDepth(texture2D(uPrevDepthTexture,screenPos));

						    if(prevDepth + uDepthOffset - gl_FragCoord.z >= 0. ){
						   	    discard;
						    }
						}
				    
					`
				)

			}

			//premultiply alpha
			function colorOnBeforeCompile(shader){

				shader.fragmentShader = shader.fragmentShader.replace('#include <packing>','')
				shader.fragmentShader = `
					#include <packing>
					uniform sampler2D uPrevColorTexture;

					${shader.fragmentShader}
				`
				//this has early return
				depthOnbeforeCompile(shader)

				shader.fragmentShader = shader.fragmentShader.replace(
					/}$/gm,
					`	
						// gl_FragColor.xyz *= gl_FragColor.a;
					}
					`
				)
			}

			let depthStencilBuffer

			function initTargets(depthStencilBuffer){
				if(targets.length)
					for( let i = 0 ; i < 3 ; i++ ){
						targets.pop().dispose()
					}

				for( let i = 0 ; i < 3 ; i ++ ){

					targets.push(
						new THREE.WebGLRenderTarget(
							window.innerWidth * dpr,
							window.innerHeight * dpr, 
							{
								minFilter: THREE.NearestFilter, 
								magFilter: THREE.NearestFilter 
							}
						)
					)

					debugScene.children[i].material.map = targets[i]

					targets[i].ownDepthBuffer = depthStencilBuffer
				
				}

				// targets[0].ownDepthBuffer = depthStencilBuffer
				// targets[1].ownDepthBuffer = depthStencilBuffer

			}

			init();

			function init() {


				// renderer
				// renderer = new THREE.WebGLRenderer({preserveDrawingBuffer: true, premultipliedAlpha: false});
				renderer = new THREE.WebGLRenderer({preserveDrawingBuffer: true, premultipliedAlpha: true});
				renderer.setSize( window.innerWidth, window.innerHeight );
				renderer.autoClear = false
				document.body.appendChild( renderer.domElement );
				renderer.setPixelRatio(dpr)
				gl = renderer.getContext()

				document.getElementById('DEPTH_BITS').innerHTML = gl.getParameter(gl.DEPTH_BITS)

				stats = new Stats();
				document.body.appendChild( stats.dom )

				renderer.gammaOutput = true;

				renderer.info.autoReset = false

				// scene
				scene = new THREE.Scene();

				scene.autoUpdate = false;

				// camera
				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
				camera.position.set( - 10, 0, 23 );
				scene.add( camera );

				// controls
				var controls = new THREE.OrbitControls( camera, renderer.domElement );
				controls.addEventListener( 'change', render );
				controls.minDistance = 10;
				controls.maxDistance = 150;
				controls.screenSpacePanning = true

				// ---------------------------DEPTH PEEL ------------------------------- //
				debugCamera = new THREE.PerspectiveCamera( 40 , window.innerWidth / window.innerHeight , 1, 10)
				debugCamera.position.z = 5
				debugScene = new THREE.Scene()
				const planeGeometry = new THREE.PlaneBufferGeometry(2,2,1,1)

				//debug
				for( let i = 0 ; i < 3 ; i ++ ){
					const plane = new THREE.Mesh(planeGeometry, new THREE.MeshBasicMaterial())
					plane.material.depthTest = false
					plane.scale.multiplyScalar(0.3)
					debugScene.add(plane)
					plane.position.x = (i - 2 + 0.5) * 0.7
					plane.position.y = 1.5
				}

				compositeMaterial = new THREE.ShaderMaterial({
					uniforms:{
						uTextureA: { value: null },
						uTextureB: { value: null },
						uBlit: { value: 0 },
						uStencil: { value: 0 }
					},
					vertexShader:`
						varying vec2 vUv;
						void main(){
							vUv = uv;
							gl_Position = vec4(position.xy,0.,1.);
						}
					`,
					fragmentShader:`
						varying vec2 vUv;
						uniform sampler2D uTextureA;
						uniform sampler2D uTextureB;
						uniform int uBlit;
						uniform int uStencil;

						void main(){
							vec4 src = texture2D(uTextureA,vUv);
							vec4 dst = texture2D(uTextureB,vUv);
							
							if(uBlit == 0 )

							{
								gl_FragColor = src;
							}

							// {
							// 	float a1 = 1.-src.a;					
							// 	gl_FragColor.a = src.a + a1 * dst.a;
							// 	gl_FragColor.rgb = src.rgb + a1 * dst.rgb;
							// 	gl_FragColor.rgb /= gl_FragColor.a;
							// } 

							else {
								gl_FragColor = src;
								gl_FragColor.xyz *= gl_FragColor.a;

								// gl_FragColor.xyz = src.aaa;
								//gl_FragColor.rgb /= gl_FragColor.a;
							} 

							if(gl_FragColor.a == 0.) discard;

							// if(uStencil == 1){
							// 	if(gl_FragColor.a < 1. && dst.a < 1. ) discard;
							// } else if (uStencil == 2){
							// 	if(gl_FragColor.a == 1. ) discard;
							// }
						}
					`,
					transparent:true,
					depthTest:false,
					depthWrite: false,

					blending: THREE.CustomBlending,

					blendEquation: THREE.AddEquation,

					blendDst: THREE.OneFactor,
					// blendDstAlpha: THREE.OneFactor,
					blendDstAlpha: null,

					blendSrc: THREE.OneMinusDstAlphaFactor,
					// blendSrcAlpha: THREE.OneMinusDstAlphaFactor,
					blendSrcAlpha: null,


				})

				paintMaterial = new THREE.ShaderMaterial({
					uniforms:{
						uColor: {value: new THREE.Color(1,0,0)},
					},
					vertexShader: compositeMaterial.vertexShader,
					fragmentShader:`
					uniform vec3 uColor;

					void main(){
						// gl_FragColor = vec4(uColor,.5);
						gl_FragColor = vec4(uColor,1.);
					}
					`,
					depthTest: false,
					depthWrite: false,
					transparent: true,

				})
				const compositePlane = new THREE.Mesh(planeGeometry,compositeMaterial)
				compositePlane.frustumCulled = false
				compositeScene.add(compositePlane)

				depthMaterial = new THREE.MeshDepthMaterial()
				depthMaterial.side = getSide()
				depthMaterial.depthPacking = THREE.RGBADepthPacking
				depthMaterial.onBeforeCompile = depthOnbeforeCompile

				depthStencilBuffer = gl.createRenderbuffer()

				initTargets(depthStencilBuffer)

				globalPeelUniforms.uScreenSize.value.set(1/(window.innerWidth * dpr),1/(window.innerHeight * dpr))
				const numItems = 10

				const boxGeometry = new THREE.BoxBufferGeometry(2,2,2,1,1,1)

				//setup objects
				for ( let i = 0 ; i < numItems ; i ++ ){

					const mesh = new THREE.Mesh(
						boxGeometry, 
						new THREE.MeshPhongMaterial({
						// new THREE.MeshBasicMaterial({
							opacity: options.opacity,
							side: getSide(),
						})
					)

					mesh.renderOrder = 1

					transparentObjects.push(mesh)
					
					mesh.material.onBeforeCompile = colorOnBeforeCompile

					mesh.material.colorA = new THREE.Color().setHSL(
						(i%2==0 ? numItems - i : i ) / numItems,
						1,
						0.5
					)

					mesh.material.colorB = new THREE.Color().setRGB(1,1,1)

					mesh.material.color.copy(mesh.material[getColorKey()])

					mesh.rotation.y = i * Math.PI/2
					mesh.scale.y = 2
					mesh.position.y = (i - numItems /2)* 2.5 
					mesh.scale.x = 20

					scene.add(mesh)
				}

				// const opaque = new THREE.MeshBasicMaterial({color:'white'})
				const opaque = new THREE.MeshPhongMaterial({color:'white'})
				opaque.onBeforeCompile = colorOnBeforeCompile

				for( let i = 0 ; i < 4 ; i ++ ){
					const m = new THREE.Mesh(boxGeometry, opaque)
					opaqueObjects.push(m)
					scene.add(m)
					const angle = i / 2 * Math.PI
					m.position.set(Math.sin(angle),0,Math.cos(angle)).multiplyScalar(10)
					m.scale.multiplyScalar(1.5)
					m.scale.y = 3.5
				}

				// ambient
				scene.add( new THREE.AmbientLight( 0xffffff, .5 ) );

				// light
				var light = new THREE.PointLight( 0xffffff, 0.8 );
				light.position.set(20,20,20)
				scene.add( light );

				window.addEventListener( 'resize', onWindowResize, false );

				scene.updateMatrixWorld()

				onModeChange()

				animate()

			}

			function renderDepthPeel2(){


				globalPeelUniforms.uLayer.value = 0
				globalPeelUniforms.uPrevDepthTexture.value = null
				compositeMaterial.uniforms.uBlit.value = 1 

				gl.colorMask(true,true,true,true)
				gl.depthMask(true)

				renderer.setRenderTarget()
				renderer.setClearColor(0x888888,1)
				renderer.clear()

				renderer.setRenderTarget( targets[2] )
				renderer.setClearColor(0,0)
				renderer.clear()

				renderer.setRenderTarget( targets[1] )
				renderer.setClearColor(0xffffff,1)
				renderer.clear()

				renderer.setRenderTarget( targets[0] )
				renderer.setClearColor(0,0)
				renderer.clear()



				gl.enable(gl.STENCIL_TEST)
				//opaque
				opaqueObjects.forEach(o=>o.visible = true)
				transparentObjects.forEach(o=>o.visible = false )

				// renderer.render( scene , camera )

				gl.stencilFunc(gl.ALWAYS, 0 , 0xff)
				gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP)

				//disable color write
				gl.colorMask(false,false,false,false)

				//render opaque 0 depth buffer
				renderer.render( scene , camera, targets[0] )

				//render opaque 1 depth buffer 
				// renderer.render( scene , camera, targets[1], false )

				//turn on transparent
				opaqueObjects.forEach(o=>o.visible = false)
				transparentObjects.forEach(o=>o.visible = true)

				//enable stencil and color
				gl.colorMask(true,true,true,true)

				gl.stencilFunc(gl.ALWAYS, 1 , 0xff)
				gl.stencilOp(gl.KEEP,gl.KEEP,gl.REPLACE)

				//set and depth
				scene.overrideMaterial = depthMaterial

				renderer.render( scene , camera, targets[1], false )

				scene.overrideMaterial = null

				gl.stencilFunc(gl.EQUAL, 1 , 0xff)
				gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP)

				renderer.setRenderTarget( targets[0] )
				renderer.clear(false,true,false)

				renderer.render( scene , camera, targets[0], false )

				compositeMaterial.blendSrc = THREE.OneMinusDstAlphaFactor
				compositeMaterial.blendDst = THREE.OneFactor

				compositeMaterial.uniforms.uTextureA.value = targets[0]
				compositeMaterial.uniforms.uBlit.value = 1 
				
				gl.stencilFunc(gl.EQUAL, 1 , 0xff)
				gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP)

				renderer.render( compositeScene , camera, targets[2] )

				// {	
				// 	compositeScene.overrideMaterial = paintMaterial
				// 	paintMaterial.transparent = false
				// 	compositeScene.overrideMaterial.depthTest = false
				// 	// paintMaterial.uniforms.uColor.value.setHSL(i/8,1,0.5)
				// 	renderer.render( compositeScene, camera, targets[2] )
				// 	compositeScene.overrideMaterial = null
				// }

				// return

				// console.log('>>>>>')
				let bit = 1
				// console.log(targets[0])
				for( let i = 0 ; i < options.layers ; i ++ ){
					// continue
					const flip = i % 2 
					const flop = (i+1)%2
				
					//next peel
					globalPeelUniforms.uPrevDepthTexture.value = targets[flop]
					globalPeelUniforms.uLayer.value = i + 1




					renderer.setRenderTarget(targets[flip])
					renderer.setClearColor(0,0)
					renderer.clear(true,true,false)

					bit |= 1<<(i+1)

					// console.log(`bit ${bit.toString(2)} mask ${ (1 << i).toString(2) }`)
					
					{
						gl.stencilFunc(gl.EQUAL, bit, 1 << i)
						gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP)

						//turn on opaque objects turn off transparent
						//
						opaqueObjects.forEach(o=>o.visible = true)
						transparentObjects.forEach(o=>o.visible = false )

						//disable color write
						gl.colorMask(false,false,false,false)
						
						//render opaque into target 
						renderer.render( scene , camera, targets[flip], false )
						
						gl.colorMask(true,true,true,true)

						opaqueObjects.forEach(o=>o.visible = false)
						transparentObjects.forEach(o=>o.visible = true )
					}

					//replace stencil with next level
					//
					gl.stencilOp(gl.KEEP,gl.KEEP,gl.REPLACE)

					//render color into target
					renderer.render( scene , camera, targets[flip], false )


					//blit to 3rd buffer
					//
					compositeMaterial.uniforms.uTextureA.value = targets[flip]

					// gl.disable(gl.STENCIL_TEST)

					gl.stencilFunc(gl.EQUAL, bit, 1<<(i+1))
					gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP)

					// compositeScene.overrideMaterial = paintMaterial
					// paintMaterial.transparent = false
					// compositeScene.overrideMaterial.depthTest = false
					// paintMaterial.uniforms.uColor.value.setHSL(i/8,1,0.5)
					renderer.render( compositeScene, camera, targets[2] )
					// compositeScene.overrideMaterial = null

					// gl.enable(gl.STENCIL_TEST)


					//clear depth target and render
					renderer.setRenderTarget(targets[flip])
					renderer.setClearColor(0xffffff,1)
					renderer.clear(true,true,false)

					scene.overrideMaterial = depthMaterial

					gl.stencilFunc(gl.EQUAL, bit, 1 << (i+1))
					gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP)

					renderer.render( scene , camera, targets[flip], false )


					// if( i === options.layers - 1 ){
					// 	gl.stencilFunc(gl.EQUAL, bit, 1<<(i+1))
					// 	gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP)
					// 	compositeScene.overrideMaterial = paintMaterial
					// 	renderer.render( compositeScene, camera, targets[flip])
					// 	compositeScene.overrideMaterial = null
					// }

					scene.overrideMaterial = null

				}

				gl.disable(gl.STENCIL_TEST)
				// 
				// gl.bindFramebuffer( gl.FRAMEBUFFER, null )

				// gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer )


				globalPeelUniforms.uLayer.value = 0
				gl.stencilFunc(gl.ALWAYS, 1, 0xff)
				gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP)
				opaqueObjects.forEach(o=>{o.visible = true;  o.depthTest = false})
				transparentObjects.forEach(o=>o.visible = false )

				renderer.render( scene , camera )

				// return
				// gl.stencilFunc(gl.EQUAL, 1, 0xff)
				compositeMaterial.uniforms.uBlit.value = 0 
				compositeMaterial.blendSrc = THREE.OneFactor
				compositeMaterial.blendDst = THREE.OneMinusSrcAlphaFactor
				compositeMaterial.blendSrcAlpha = null
				compositeMaterial.blendDstAlpha = null
				compositeMaterial.uniforms.uTextureA.value = targets[2]

				renderer.render( compositeScene , camera )

			}

			function renderDepthPeel(){

				//clear main frame
				renderer.setClearColor(0x000,1)
				renderer.clear()

				globalPeelUniforms.uLayer.value = 0

				//render first depth
				scene.overrideMaterial = depthMaterial
				renderer.setClearColor(0xffffff,1)
				renderer.render( scene, camera, targets[0], true)

				//first color
				scene.overrideMaterial = null
				renderer.setClearColor(0x000,0)
				renderer.render( scene, camera, targets[2], true)

				for( let i = 0 ; i < options.layers ; i ++ ){
					
					const a = i % 3 //shift these around
					const b = (i+1) % 3
					const c = (i+2) % 3
					const d = 3 //peel into this

					globalPeelUniforms.uPrevDepthTexture.value = targets[a]
					globalPeelUniforms.uLayer.value = i + 1

					//render next depth
					scene.overrideMaterial = depthMaterial
					renderer.setClearColor(0xffffff,1)
					renderer.render( scene, camera, targets[b], true)
					
					//peel
					scene.overrideMaterial = null
					renderer.setClearColor(0x000,0)
					renderer.render( scene, camera, targets[d], true)

					//combine
					compositeMaterial.uniforms.uTextureA.value = targets[c]
					compositeMaterial.uniforms.uTextureB.value = targets[d]
					// renderer.render( compositeScene, camera, targets[a], true)

					compositeMaterial.uniforms.uBlit.value = 1 
					renderer.render( compositeScene, camera )
				}

				//render final result over opaque objects
				globalPeelUniforms.uPrevDepthTexture.value = null

				transparentObjects.forEach(o=>o.visible = false)
				
				// renderer.render( scene, camera )
				// renderer.render( compositeScene, camera )

				transparentObjects.forEach(o=>o.visible = true)
			}

			function onWindowResize() {

				renderer.setSize( window.innerWidth * dpr, window.innerHeight * dpr );

				globalPeelUniforms.uScreenSize.value.set(1/(window.innerWidth * dpr),1/(window.innerHeight * dpr))

				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

				debugCamera.aspect = camera.aspect
				debugCamera.updateProjectionMatrix()
				
				initTargets(depthStencilBuffer)
			
			}

			function animate() {

				requestAnimationFrame( animate )

				stats.update()

				camera.updateMatrixWorld()

				render()

			}

			function render() {

				renderer.info.reset()

				if(!options.enabled){
					renderer.setClearColor(0x888888,1)
					renderer.clear()
					renderer.render( scene, camera )
				
				} else {

					// renderDepthPeel()
					renderDepthPeel2()
					gl.disable(gl.STENCIL_TEST)
					if(options.debug)
					renderer.render( debugScene , debugCamera )
				
				}

			}

		</script>

	</body>
</html>
